package cn.crystalorigin.plugin.util.delegate

import cn.crystalorigin.plugin.util.MSTimer
import cn.crystalorigin.plugin.util.TimeUnit
import java.util.function.Supplier
import kotlin.experimental.ExperimentalTypeInference
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

@OptIn(ExperimentalTypeInference::class)
class CachedValue<T>(
    private val defaultDelay: Int,
    @BuilderInference private val invalidValue: T? = null,
    @BuilderInference private val block: () -> T
) : Supplier<T>, ReadOnlyProperty<Any?, T> {
    private var value: T? = null
    private val timer = MSTimer()

    fun get(updateTime: Int, timeUnit: TimeUnit): T {
        return get(updateTime * timeUnit.multiplier)
    }

    fun get(updateTime: Long, timeUnit: TimeUnit): T {
        return get(updateTime * timeUnit.multiplier)
    }

    fun get(updateTime: Int): T {
        val cached = value

        return if (cached == null || cached == invalidValue || timer.check(updateTime)) {
            timer.reset()
            block().also { value = it }
        } else {
            cached
        }
    }

    fun get(updateTime: Long): T {
        val cached = value

        return if (cached == null || cached == invalidValue || timer.check(updateTime)) {
            timer.reset()
            block().also { value = it }
        } else {
            cached
        }
    }

    fun getForce(): T {
        return block().also { value = it }
    }

    fun getLazy(): T? {
        return value
    }

    fun updateForce() {
        timer.reset()
        value = block()
    }

    fun updateLazy() {
        value = null
    }

    fun wrapped(updateTime: Long): ReadWriteProperty<Any?, T> {
        return object : ReadWriteProperty<Any?, T> {
            override fun getValue(thisRef: Any?, property: KProperty<*>): T {
                return this@CachedValue.get(updateTime)
            }

            override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
                this@CachedValue.value = value
            }
        }
    }

    fun wrapped(updateTime: Long, timeUnit: TimeUnit): ReadWriteProperty<Any?, T> {
        return object : ReadWriteProperty<Any?, T> {
            override fun getValue(thisRef: Any?, property: KProperty<*>): T {
                return this@CachedValue.get(updateTime, timeUnit)
            }

            override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
                this@CachedValue.value = value
            }
        }
    }

    override fun get(): T {
        return getForce()
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T = get(defaultDelay)
}